enum TraceLevel {
  all = 0,
  debug = 1,
  info = 2,
  warn = 3,
  error = 4,
  none = 1000
}
enum TraceStep {
  initializing = 'initializing'
}
interface TraceBuffer {
  data: number | string | object
  level: TraceLevel
  step: TraceStep
  time: Date
}
interface TraceOptions {
  traceLevel?: TraceLevel
}
type TraceCallback = () => TraceBuffer['data']
type TraceLevelCallback = (cb: TraceCallback) => void

class Trace {
  private readonly options: Required<TraceOptions>
  public error: TraceLevelCallback
  public warn: TraceLevelCallback
  public info: TraceLevelCallback
  public debug: TraceLevelCallback
  public traceAll: TraceLevelCallback
  private traceBuffer: TraceBuffer[]
  private step: TraceStep
  constructor(options: TraceOptions = {}) {
    this.options = {
      traceLevel: Trace.TRACE_DEBUG,
      ...options
    }
    this.traceBuffer = []
    this.step = TraceStep.initializing
    this.error = this.trace.bind(this, Trace.TRACE_ERROR)
    this.warn = this.trace.bind(this, Trace.TRACE_WARN)
    this.info = this.trace.bind(this, Trace.TRACE_INFO)
    this.debug = this.trace.bind(this, Trace.TRACE_DEBUG)
    this.traceAll = this.trace.bind(this, Trace.TRACE_ALL)
  }
  static get TRACE_ALL() {
    return TraceLevel.all
  }
  static get TRACE_DEBUG() {
    return TraceLevel.debug
  }
  static get TRACE_INFO() {
    return TraceLevel.info
  }
  static get TRACE_WARN() {
    return TraceLevel.warn
  }
  static get TRACE_ERROR() {
    return TraceLevel.error
  }
  static get TRACE_NONE() {
    return TraceLevel.none
  }

  trace(level: TraceLevel, cb: TraceCallback) {
    if (typeof cb !== 'function') {
      throw new Error(
        `Lance trace was called but instead of passing a function, it received a [${typeof cb}]`
      )
    }
    if (level < this.options.traceLevel) return
    const a = new Date()
    this.traceBuffer.push({
      data: cb(),
      level,
      step: this.step,
      time: new Date()
    })
  }

  rotate() {
    const buffer = this.traceBuffer
    this.traceBuffer = []
    return buffer
  }

  get length() {
    return this.traceBuffer.length
  }

  setStep(s: TraceStep) {
    this.step = s
  }
}

export default Trace
